/*
 * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <stdint.h>

#include <esp_err.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "freertos/stream_buffer.h"

#include "slip.h"

/* Encode: encode a packet of length "inlen", starting at location "inbuf".
 */
esp_err_t slip_encode(const uint8_t *inbuf, uint16_t inlen, uint8_t **outbuf, uint16_t *outlen)
{
    /* send an initial END character to flush out any data that may
     * have accumulated in the receiver due to line noise
     */
    char c = SLIP_END;
    StreamBufferHandle_t stream_buffer = xStreamBufferCreate(inlen * 2, 8);
    xStreamBufferSend(stream_buffer, &c, 1, 0);

	/* for each byte in the packet, send the appropriate character
	 * sequence
	 */
	while (inlen --) {
		switch (*inbuf) {
            /* if it's the same code as an END character, we send a
             * special two character code so as not to make the
             * receiver think we sent an END
             */
            case SLIP_END:
                c = SLIP_ESC;
                xStreamBufferSend(stream_buffer, &c, 1, 0);
                c = SLIP_ESC_END;
                xStreamBufferSend(stream_buffer, &c, 1, 0);
                break;

			/* if it's the same code as an ESC character,
			 * we send a special two character code so as not
			 * to make the receiver think we sent an ESC
			 */
            case SLIP_ESC:
                c = SLIP_ESC;
                xStreamBufferSend(stream_buffer, &c, 1, 0);
                c = SLIP_ESC_ESC;
                xStreamBufferSend(stream_buffer, &c, 1, 0);
                break;
                
            /* otherwise, we just send the character
             */
            default:
                xStreamBufferSend(stream_buffer, inbuf, 1, 0);
                break;
		}
		inbuf ++;
	}

    /* tell the receiver that we're done sending the packet
     */
    c = SLIP_END;
    xStreamBufferSend(stream_buffer, &c, 1, 0);

    *outlen = xStreamBufferBytesAvailable(stream_buffer);
    if (*outlen) {
        *outbuf = calloc(1, *outlen + 1);
        xStreamBufferReceive(stream_buffer, *outbuf, *outlen, 100 / portTICK_PERIOD_MS);
    }

    vStreamBufferDelete(stream_buffer);

    return ESP_OK;
}

/* Decode: decode a packet into the buffer located at "inbuf".
 * If more than inlen bytes are received, the packet will be truncated.
 * Returns the number of bytes stored in the buffer.
 */
esp_err_t slip_decode(const uint8_t *inbuf, uint16_t inlen, uint8_t **outbuf, uint16_t *outlen)
{
    char c = SLIP_END;
    uint16_t received = 0;
    uint8_t *output = calloc(1, inlen * 2);

    StreamBufferHandle_t stream_buffer = xStreamBufferCreate(inlen, 8);
    xStreamBufferSend(stream_buffer, inbuf, inlen, 0);

    while (1) {
        xStreamBufferReceive(stream_buffer, &c, 1, 100 / portTICK_PERIOD_MS);
        switch(c) {
            /* if it's an END character then we're done with
            * the packet
            */
            case SLIP_END:
                /* a minor optimization: if there is no
                * data in the packet, ignore it. This is
                * meant to avoid bothering IP with all
                * the empty packets generated by the
                * duplicate END characters which are in
                * turn sent to try to detect line noise.
                */
                if (!xStreamBufferBytesAvailable(stream_buffer)) {
                    goto slip_finish;
                }
                break;

            /* if it's the same code as an ESC character, wait
            * and get another character and then figure out
            * what to store in the packet based on that.
            */
            case SLIP_ESC:
                xStreamBufferReceive(stream_buffer, &c, 1, 100 / portTICK_PERIOD_MS);

                /* if "c" is not one of these two, then we
                * have a protocol violation.  The best bet
                * seems to be to leave the byte alone and
                * just stuff it into the packet
                */
                switch(c) {
                    case SLIP_ESC_END:
                        c = SLIP_END;
                        break;
                    case SLIP_ESC_ESC:
                        c = SLIP_ESC;
                        break;
                    default:
                        break;
                }
                output[received ++] = c;
                break;
            /* here we fall into the default handler and let
            * it store the character for us
            */
            default:
                if (!xStreamBufferBytesAvailable(stream_buffer)) {
                    goto slip_finish;
                }
                output[received ++] = c;
                break;
        }
    }

slip_finish:
    vStreamBufferDelete(stream_buffer);
    *outbuf = output;
    *outlen = received;

    return ESP_OK;
}
